/*  Graph JavaScript framework, version 0.0.1
 *  (c) 2006 Aslak Hellesoy <aslak.hellesoy@gmail.com>
 *  (c) 2006 Dave Hoover <dave.hoover@gmail.com>
 *
 *  Ported from Graph::Layouter::Spring in
 *    http://search.cpan.org/~pasky/Graph-Layderer-0.02/
 *  The algorithm is based on a spring-style layouter of a Java-based social
 *  network tracker PieSpy written by Paul Mutton E<lt>paul@jibble.orgE<gt>.
 *
 *  Graph is freely distributable under the terms of an MIT-style license.
 *  For details, see the Graph web site: http://dev.buildpatternd.com/trac
 *
 *  Modified by Aaron Hong 2009
 *--------------------------------------------------------------------------*/


//must create nodes before adding edges
var Graph = Class.create();
Graph.prototype = {
        initialize: function() {
                this.nodeSet = {};
                this.edgeSet = {};
                this.nodes = [];
                this.edges = [];
        },

        addNode: function(name, x, y) {
                var node = this.nodeSet[name];
                if(node === undefined) {
                    node = new Graph.Node(name, 0, 0);
                    this.nodeSet[name] = node;
                    this.nodes.push(node);
                }

                if(x !== undefined && y !== undefined){
                    node.layoutPosX = x;
                    node.layoutPosY = y;
                    node.x = x;
                    node.y = y;
                }

                return node;
        },

        // Uniqueness must be ensured by caller
        addEdge: function(source, target) {
            var key;
            if(source < target){
                key = source+target;
            }else{
                key = target+source;
            }
            if(this.edgeSet[key] === undefined){
                var s = this.addNode(source);
                var t = this.addNode(target);
                var edge = {source: s, target: t, distanceX: t.layoutPosX - s.layoutPosX, distanceY: t.layoutPosY - s.layoutPosY};
                this.edges.push(edge);
                this.edgeSet[key] = edge;
            }
        }
};

Graph.Node = Class.create();
Graph.Node.prototype = {
        initialize: function(name, x, y) {
                this.name = name;
                this.layoutPosX = x;
                this.layoutPosY = y;
        }
};

Graph.Layout = {};
Graph.Layout.Spring = Class.create();
Graph.Layout.Spring.prototype = {
        initialize: function(graph) {
                this.zoomFactor = 1;
                this.graph = graph;
                this.iterations = 500;
                this.maxRepulsiveForceDistance = 6;
                this.k = 2;
                this.c = 0.01;
                this.maxVertexMovement = 0.5;
        },

        layout: function(zoom) {
                this.zoomFactor = zoom;
                this.layoutPrepare();
                for (var i = 0; i < this.iterations; i++) {
                        this.layoutIteration();
                }
                this.layoutCalcBounds();
        },

        layoutPrepare: function() {
            for (var i = 0; i < this.graph.nodes.length; i++) {
                    var node = this.graph.nodes[i];
                        node.layoutForceX = 0;
                        node.layoutForceY = 0;
                }
        },

        layoutCalcBounds: function() {
                var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;

                for (var i = 0; i < this.graph.nodes.length; i++) {
                        var x = this.graph.nodes[i].layoutPosX;
                        var y = this.graph.nodes[i].layoutPosY;

                        if(x > maxx) { maxx = x; }
                        if(x < minx) { minx = x; }
                        if(y > maxy) { maxy = y; }
                        if(y < miny) { miny = y; }
                }

                this.graph.layoutMinX = minx;
                this.graph.layoutMaxX = maxx;
                this.graph.layoutMinY = miny;
                this.graph.layoutMaxY = maxy;
        },

        setCalcBounds: function(minx, miny, maxx, maxy){
            this.graph.layoutMinX = minx;
            this.graph.layoutMaxX = maxx;
            this.graph.layoutMinY = miny;
            this.graph.layoutMaxY = maxy;
        },

        layoutIteration: function() {
            var i;
                // Forces on nodes due to node-node repulsions
            for (i = 0; i < this.graph.nodes.length; i++) {
                    var node1 = this.graph.nodes[i];
 //                   //console.log("node " + node1.name);
                    this.layoutAttractiveNode(node1);
                    for (var j = i + 1; j < this.graph.nodes.length; j++) {
                            var node2 = this.graph.nodes[j];
                                this.layoutRepulsive(node1, node2);
                        }
                }
                // Forces on nodes due to edge attractions
//            for (var i = 0; i < this.graph.edges.length; i++) {
//                    var edge = this.graph.edges[i];
//                        this.layoutAttractive(edge);
//                }

                // Move by the given force
            for (i = 0; i < this.graph.nodes.length; i++) {
                    var node = this.graph.nodes[i];
                        var xmove = this.c * node.layoutForceX;
                        var ymove = this.c * node.layoutForceY;

                        var max = this.maxVertexMovement;
                        if(xmove > max) { xmove = max; }
                        if(xmove < -max) { xmove = -max; }
                        if(ymove > max) { ymove = max; }
                        if(ymove < -max) { ymove = -max; }

                        if(isNaN(xmove) || isNaN(ymove)){
                            //console.log("move is NaN");
                        }

                        node.layoutPosX += xmove;
                        node.layoutPosY += ymove;

                        if(isNaN(node.layoutPosX) || isNaN(node.layoutPosY)){
                            //console.log("layoutPos is NaN");
                        }

                        node.layoutForceX = 0;
                        node.layoutForceY = 0;
                }
        },

        layoutRepulsive: function(node1, node2) {
                var dx = node2.layoutPosX - node1.layoutPosX;
                var dy = node2.layoutPosY - node1.layoutPosY;
                var d2 = dx * dx + dy * dy;
                if(d2 < 0.01) {
                        dx = 0.1 * Math.random() + 0.1;
                        dy = 0.1 * Math.random() + 0.1;
                        d2 = dx * dx + dy * dy;
                }
//				var dirX = 1;
//				var dirY = 1;
//				if(dx < 0){
//					dirX = -1;
//				}
//				if(dy < 0){
//					dirY = -1;
//				}
//
//                if(dy < 0.1) {
//                        dy = 0.1 * Math.random() + 0.1;
//                }
//                if(dx < 0.1) {
//                        dx = 0.1 * Math.random() + 0.1;
//                }
                var d = Math.sqrt(d2);
                if(d < this.maxRepulsiveForceDistance) {
                        var repulsiveForceX = this.k/(dx * dx * dx);
                        var repulsiveForceY = this.k/(dy * dy * dy);

//						//console.log("repulsiveX" + repulsiveForceX);
//						//console.log("repulsiveY" + repulsiveForceY);

                        if(isNaN(repulsiveForceX) || isNaN(repulsiveForceY)){
                            //console.log("repulsive is NaN");
                        }
                        var repulsiveForce = this.k * this.k / d2;
                        repulsiveForce = repulsiveForce/ this.zoomFactor;
                        //var repulsiveForce = .1 * .1 / d2;
                        node2.layoutForceX += repulsiveForce * dx / d;
                        node2.layoutForceY += repulsiveForce * dy / d;
                        node1.layoutForceX -= repulsiveForce * dx / d;
                        node1.layoutForceY -= repulsiveForce * dy / d;

//                        node2.layoutForceX += repulsiveForceX * dirX;
//                        node2.layoutForceY += repulsiveForceY * dirY;
//                        node1.layoutForceX -= repulsiveForceX * dirX;
//                        node1.layoutForceY -= repulsiveForceY * dirY;
                        if(isNaN(node1.layoutForceX) || isNaN(node1.layoutForceY)){
                            //console.log("rep layoutForce is NaN");
                        }
                        if(isNaN(node2.layoutForceX) || isNaN(node2.layoutForceY)){
                            //console.log("rep layoutForce is NaN");
                        }
                }
        },

        layoutAttractiveNode: function(node) {
                var dx = node.x - node.layoutPosX;
                var dy = node.y - node.layoutPosY;
                var d2 = dx * dx + dy * dy;
                if(d2 < 0.01) {
                        dx = 0.1 * Math.random() + 0.1;
                        dy = 0.1 * Math.random() + 0.1;
                        d2 = dx * dx + dy * dy;
                }
                var d = Math.sqrt(d2);
//                if(d > this.maxRepulsiveForceDistance) {
//                        d = this.maxRepulsiveForceDistance;
//                        d2 = d * d;
//                }
//                var attractiveForce = (d2 - edge.distance * edge.distance) / this.k;
//                if(edge.weight == undefined || edge.weight < 1) edge.weight = 1;
//                attractiveForce *= Math.log(edge.weight) * 0.5 + 1;
                var attractiveForceX = (dx * dx) * this.k * this.zoomFactor;
                var attractiveForceY = (dy * dy) * this.k * this.zoomFactor;

//				//console.log("attractiveForceX" + attractiveForceX);
//				//console.log("attractiveForceY" + attractiveForceY);

                if(isNaN(attractiveForceX) || isNaN(attractiveForceY)){
                    //console.log("attractiveNode is NaN");
                }

                var dirX = 1;
                var dirY = 1;
                if(dx < 0){
                    dirX = -1;
                }
                if(dy < 0){
                    dirY = -1;
                }

                //node2.layoutForceX -= attractiveForce * dx / d;
//                node2.layoutForceY -= attractiveForce * dy / d;
//                node1.layoutForceX += attractiveForce * dx / d;
//                node1.layoutForceY += attractiveForce * dy / d;
                node.layoutForceX += attractiveForceX * dirX;
                node.layoutForceY += attractiveForceY * dirY;
                if(isNaN(node.layoutForceX) || isNaN(node.layoutForceY)){
                    //console.log("att layoutForce is NaN");
                }
        },

        layoutAttractive: function(edge) {
                var node1 = edge.source;
                var node2 = edge.target;

                var dx = node2.layoutPosX - node1.layoutPosX;
                var dy = node2.layoutPosY - node1.layoutPosY;
                var d2 = dx * dx + dy * dy;
                if(d2 < 0.01) {
                        dx = 0.1 * Math.random() + 0.1;
                        dy = 0.1 * Math.random() + 0.1;
                        d2 = dx * dx + dy * dy;
                }
                var d = Math.sqrt(d2);
//                if(d > this.maxRepulsiveForceDistance) {
//                        d = this.maxRepulsiveForceDistance;
//                        d2 = d * d;
//                }
//                var attractiveForce = (d2 - edge.distance * edge.distance) / this.k;
//                if(edge.weight == undefined || edge.weight < 1) edge.weight = 1;
//                attractiveForce *= Math.log(edge.weight) * 0.5 + 1;
                var attractiveForceX = (dx * dx - edge.distanceX * edge.distanceX) * this.k;
                var attractiveForceY = (dy * dy - edge.distanceY * edge.distanceY) * this.k;

                if(isNaN(attractiveForceX) || isNaN(attractiveForceY)){
                    //console.log("attractive is NaN");
                }

                var dirX = 1;
                var dirY = 1;
                if(dx < 0){
                    dirX = -1;
                }
                if(dy < 0){
                    dirY = -1;
                }

                //node2.layoutForceX -= attractiveForce * dx / d;
//                node2.layoutForceY -= attractiveForce * dy / d;
//                node1.layoutForceX += attractiveForce * dx / d;
//                node1.layoutForceY += attractiveForce * dy / d;
                node2.layoutForceX -= attractiveForceX * dirX;
                node2.layoutForceY -= attractiveForceY * dirY;
                node1.layoutForceX += attractiveForceX * dirX;
                node1.layoutForceY += attractiveForceY * dirY;
        }
};
